#!/usr/bin/python
# PYTHON_ARGCOMPLETE_OK

from __future__ import print_function

import argparse
import getpass
import os
import platform
import shlex
import struct
import subprocess
import sys
from base64 import b64decode, b64encode
from distutils.util import strtobool

import argcomplete

from webdav3.client import Client, WebDavException, NotConnection, Urn


def get_terminal_size():
    current_os = platform.system()
    tuple_xy = None
    if current_os == 'Windows':
        tuple_xy = _get_terminal_size_windows()
        if tuple_xy is None:
            tuple_xy = _get_terminal_size_input()
    if current_os in ['Linux', 'Darwin'] or current_os.startswith('CYGWIN'):
        tuple_xy = _get_terminal_size_linux()
        if tuple_xy is None:
            tuple_xy = 80, 25
    return tuple_xy


def _get_terminal_size_windows():
    try:
        from ctypes import windll, create_string_buffer
        h = windll.kernel32.GetStdHandle(-12)
        csbi = create_string_buffer(22)
        res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
        if res:
            (bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
            sizex = right - left + 1
            sizey = bottom - top + 1
        return sizex, sizey
    except:
        pass


def _get_terminal_size_input():
    try:
        cols = int(subprocess.check_call(shlex.split('tput cols')))
        rows = int(subprocess.check_call(shlex.split('tput lines')))
        return (cols, rows)
    except:
        pass


def _get_terminal_size_linux():

    def ioctl_GWINSZ(fd):
        try:
            import fcntl
            import termios
            return struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
        except:
            pass
    cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
    if not cr:
        try:
            fd = os.open(os.ctermid(), os.O_RDONLY)
            cr = ioctl_GWINSZ(fd)
            os.close(fd)
        except:
            pass
    if not cr:
        try:
            cr = (os.environ['LINES'], os.environ['COLUMNS'])
        except:
            return None
    return int(cr[1]), int(cr[0])


class ProgressBar:

    def __init__(self):
        self.precise = 0
        self.total = 0

    def show(self):
        progress_line = self._progress_line()
        import sys
        if self.precise == 100:
            print(progress_line, end="")
        else:
            print(progress_line, end="")
            sys.stdout.write("\r")

    def _progress_line(self):
        sizex, sizey = get_terminal_size()
        available_width = sizex
        precise = self._get_field_precise()
        ratio = self._get_field_ratio()
        available_width -= len(precise) + len(ratio)
        progress = self._get_field_progress(available_width-2)
        return "{precise} {progress} {ratio}".format(precise=precise, progress=progress, ratio=ratio)

    def _get_field_precise(self):
        line = "{precise}%".format(precise=self.precise)
        return "{:<4}".format(line)

    def _get_field_ratio(self):
        current = float(self.precise * self.total / 100)
        total = float(self.total)
        current_line = "{:.2f}".format(current)
        total_line = "{:.2f}".format(total)
        line = "{current}/{total}".format(current=current_line, total=total_line)
        available = len(total_line) * 2 + 1
        format_line = "{prefix}{value}{sufix}".format(prefix="{:>", value=available, sufix="}")
        line = format_line.format(line)
        return line

    def _get_field_progress(self, width):
        available_width = width - 2
        current = int(self.precise * available_width / 100)
        available_width -= current + 1
        tip = ">" if not self.precise == 100 else ""
        progress = "{arrow}{tip}{space}".format(arrow="=" * current, tip=tip, space=" " * available_width)
        return "[{progress}]".format(progress=progress)

    def callback(self, current, total):
        if total and not self.total:
            self.total = total
        if not total:
            return
        precise = int(float(current) * 100 / total)
        if self.precise == precise:
            return
        else:
            self.precise = precise
            self.show()

setting_keys = ['webdav_hostname', 'webdav_root', 'webdav_login', 'webdav_password', 'webdav_token',
                'proxy_hostname', 'proxy_login', 'proxy_password',
                'cert_path', 'key_path']

crypto_keys = ['webdav_password', 'webdav_token', 'proxy_password']


def encoding(source):

    if not source:
        return ""
    return b64encode(source.encode('utf-8'))


def decoding(source):

    if not source:
        return ""
    return b64decode(source).decode('utf-8')


def import_options():

    options = dict()
    for setting_key in setting_keys:
        options[setting_key] = os.environ.get(setting_key.upper())

    for crypto_key in crypto_keys:
        if not options[crypto_key]:
            continue
        options[crypto_key] = decoding(options[crypto_key])

    return options


def valid(options):

    if 'webdav_hostname' not in options:
        return False

    if not options['webdav_token'] and not options['webdav_login']:
        return False

    if options['webdav_login'] and not options['webdav_password']:
        return False

    return True


class Formatter(argparse.RawTextHelpFormatter):

    def _get_default_metavar_for_optional(self, action):
        if not action.option_strings:
            return action.dest
        else:
            return ""

    def _format_action_invocation(self, action):
        if not action.option_strings:
            default = self._get_default_metavar_for_optional(action)
            metavar, = self._metavar_formatter(action, default)(1)
            return metavar

        else:
            parts = []

            if action.nargs == 0:
                parts.extend(action.option_strings)
            else:
                default = self._get_default_metavar_for_optional(action)
                args_string = self._format_args(action, default)
                for option_string in action.option_strings:
                    parts.append(option_string)

                return '%s %s' % (', '.join(parts), args_string)

            return ', '.join(parts)

    def _metavar_formatter(self, action, default_metavar):
        if action.metavar is not None:
            result = action.metavar
        else:
            result = default_metavar

        def format(tuple_size):
            if isinstance(result, tuple):
                return result
            else:
                return (result, ) * tuple_size
        return format


def logging_exception(exception):
    print(exception)


def urn_completer(prefix, **kwargs):

    options = import_options()
    try:
        client = Client(options)

        prefix_urn = Urn(prefix)
        if prefix_urn.is_dir():
            return (prefix+filename for filename in client.list(prefix_urn.path()))
        else:
            parent = prefix_urn.parent()
            prefix_filename = prefix_urn.filename()
            prefix_filename_length = len(prefix_filename)
            return (prefix + filename[prefix_filename_length:] for filename in client.list(parent) if filename.startswith(prefix_filename))
    except WebDavException:
        pass

    return tuple()

if __name__ == "__main__":

    epilog = """
    Examples:
    --------
    $ wdc login https://webdav.server.ru
    webdav_login: login
    webdav_password: password
    success
    $ wdc login https://webdav.server.ru --token xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    not success
    $ wdc check
    success
    $ wdc check file1
    not success
    $ wdc free
    245234120344
    $ wdc ls dir1
    file1
    ...
    fileN
    $ wdc mkdir dir2
    $ wdc copy dir1/file1 -t dir2/file1
    $ wdc move dir2/file1 -t dir2/file2
    $ wdc download dir1/file1 -t ~/Downloads/file1
    $ wdc download dir1/ -t ~/Downloads/dir1/
    $ wdc upload dir2/file2 -f ~/Documents/file1
    $ wdc upload dir2/ -f ~/Documents/
    $ wdc publish di2/file2
    https://yadi.sk/i/vWtTUcBucAc6k
    $ wdc unpublish dir2/file2
    $ wdc pull dir1/ -t ~/Documents/dir1/
    $ wdc push dir1/ -f ~/Documents/dir1/
    $ wdc info dir1/file1
    {'name': 'file1', 'modified': 'Thu, 23 Oct 2014 16:16:37 GMT',
    'size': '3460064', 'created': '2014-10-23T16:16:37Z'}
    """

    usage = """
    wdc [-h] [-v]
    wdc login https://webdav.server.ru [--token] [-r] [-p] [-c] [-k]
    wdc [action] [path] [-t] [-f]
    """

    actions = "login logout check info free ls clean mkdir copy move download upload publish unpublish push pull".split()
    actions_help = "check, info, free, ls, clean, mkdir, copy, move,\ndownload, upload, publish, unpublish, push, pull"

    parser = argparse.ArgumentParser(prog='wdc', formatter_class=Formatter, epilog=epilog, usage=usage)
    parser.add_argument("action", help=actions_help, choices=actions)

    from webdav3.client import __version__ as version
    version_text = "{name} {version}".format(name="%(prog)s", version=version)
    parser.add_argument("-v", '--version', action='version', version=version_text)
    parser.add_argument("-r", "--root", help="example: dir1/dir2")
    parser.add_argument("--token", help="example: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
    parser.add_argument("-c", "--cert-path", help="example: /etc/ssl/certs/certificate.crt")
    parser.add_argument("-k", "--key-path", help="example: /etc/ssl/private/certificate.key")
    parser.add_argument("-p", "--proxy", help="example: http://127.0.0.1:8080")
    parser.add_argument("path", help="example: dir1/dir2/file1", nargs='?').completer = urn_completer
    parser.add_argument("-f", '--from-path', help="example: ~/Documents/file1")
    parser.add_argument("-t", "--to-path", help="example for download and pull: ~/Download/file1\nexample for copy and move: dir1/dir2").completer = urn_completer

    argcomplete.autocomplete(parser, exclude=("-h", "--help", "--proxy", "-p", "-r", "--root", "-c", "--cert-path", "-t", "--to-path", "-v", "--version", "-f", "--from-path", "-k", "--key-path"))
    args = parser.parse_args()
    action = args.action

    if action == 'login':
        env = dict()
        if not args.path:
            try:
                env['webdav_hostname'] = raw_input("webdav_hostname: ")
            except NameError:
                env['webdav_hostname'] = input("webdav_hostname: ")
        else:
            env['webdav_hostname'] = args.path

        if not args.token:
            try:
                env['webdav_login'] = raw_input("webdav_login: ")
            except NameError:
                env['webdav_login'] = input("webdav_login: ")
            env['webdav_password'] = getpass.getpass("webdav_password: ")
        else:
            env['webdav_token'] = args.token

        if args.proxy:
            env['proxy_hostname'] = args.proxy
            try:
                env['proxy_login'] = raw_input("proxy_login: ")
            except NameError:
                env['proxy_login'] = input("proxy_login: ")
            env['proxy_password'] = getpass.getpass("proxy_password: ")

        if args.root:
            env['webdav_root'] = args.root

        if args.cert_path:
            env['cert_path'] = args.cert_path

        if args.key_path:
            env['key_path'] = args.key_path

        try:
            client = Client(env)
            check = client.check()
            text = "success" if check else "not success"
            print(text)

            if check:
                for crypto_key in crypto_keys:
                    if crypto_key not in env:
                        continue
                    if not env[crypto_key]:
                        continue
                    env[crypto_key] = encoding(env[crypto_key])

                for (key, value) in env.items():
                    os.putenv(key.upper(), value)
                os.system('bash')

        except WebDavException as e:
            print("not success")
            sys.exit()
    else:
        options = import_options()
        if not valid(options):
            print("First log on webdav server using the following command: wdc login.")
            sys.exit()

        elif action == "logout":
            os.system("exit")

        elif action == 'check':
            options = import_options()
            try:
                client = Client(options)
                connection = client.check()
                if not connection:
                    raise NotConnection(options["webdav_hostname"])
                check = client.check(args.path) if args.path else client.check()
                text = "success" if check else "not success"
                print(text)
            except WebDavException as e:
                logging_exception(e)

        elif action == 'free':
            options = import_options()
            try:
                client = Client(options)
                connection = client.check()
                if not connection:
                    raise NotConnection(options["webdav_hostname"])
                free_size = client.free()
                print(free_size)
            except WebDavException as e:
                logging_exception(e)

        elif action == 'ls':
            options = import_options()
            try:
                client = Client(options)
                connection = client.check()
                if not connection:
                    raise NotConnection(options["webdav_hostname"])
                paths = client.list(args.path) if args.path else client.list()
                for path in paths:
                    print(path)
            except WebDavException as e:
                logging_exception(e)

        elif action == 'clean':
            if not args.path:
                parser.print_help()
            else:
                options = import_options()
                try:
                    client = Client(options)
                    connection = client.check()
                    if not connection:
                        raise NotConnection(options["webdav_hostname"])
                    client.clean(args.path)
                except WebDavException as e:
                    logging_exception(e)

        elif action == 'mkdir':
            if not args.path:
                parser.print_help()
            else:
                options = import_options()
                try:
                    client = Client(options)
                    connection = client.check()
                    if not connection:
                        raise NotConnection(options["webdav_hostname"])
                    client.mkdir(args.path)
                except WebDavException as e:
                    logging_exception(e)

        elif action == 'copy':
            if not args.path or not args.to_path:
                parser.print_help()
            else:
                options = import_options()
                try:
                    client = Client(options)
                    connection = client.check()
                    if not connection:
                        raise NotConnection(options["webdav_hostname"])
                    client.copy(remote_path_from=args.path, remote_path_to=args.to_path)
                except WebDavException as e:
                    logging_exception(e)

        elif action == 'move':
            if not args.path or not args.to_path:
                parser.print_help()
            else:
                options = import_options()
                try:
                    client = Client(options)
                    connection = client.check()
                    if not connection:
                        raise NotConnection(options["webdav_hostname"])
                    client.move(remote_path_from=args.path, remote_path_to=args.to_path)
                except WebDavException as e:
                    logging_exception(e)

        elif action == 'download':
            if not args.path or not args.to_path:
                parser.print_help()
            else:
                options = import_options()
                progress_bar = ProgressBar()

                def download_progress(download_t, download_d, upload_t, upload_d):
                    progress_bar.callback(current=download_d, total=download_t)

                try:
                    client = Client(options)
                    connection = client.check()
                    if not connection:
                        raise NotConnection(options["webdav_hostname"])
                    if not os.path.exists(path=args.to_path):
                        client.download(remote_path=args.path, local_path=args.to_path, progress=download_progress)
                        print("\n")
                    else:
                        try:
                            choice = raw_input("Local path exists, do you want to overwrite it? [Y/n] ")
                        except NameError:
                            choice = input("Local path exists, do you want to overwrite it? [Y/n] ")
                        try:
                            yes = strtobool(choice.lower())
                            if yes:
                                client.download(remote_path=args.path, local_path=args.to_path, progress=download_progress)
                                print("\n")
                        except ValueError:
                            print("Incorrect answer")
                except WebDavException as e:
                    logging_exception(e)

        elif action == 'upload':
            if not args.path or not args.from_path:
                parser.print_help()
            else:
                options = import_options()
                progress_bar = ProgressBar()

                def upload_progress(download_t, download_d, upload_t, upload_d):
                    progress_bar.callback(current=upload_d, total=upload_t)

                try:
                    client = Client(options)
                    connection = client.check()
                    if not connection:
                        raise NotConnection(options["webdav_hostname"])
                    if not client.check(remote_path=args.path):
                        client.upload(remote_path=args.path, local_path=args.from_path, progress=upload_progress)
                        print("\n")
                    else:
                        try:
                            choice = raw_input("Remote resource exists, do you want to overwrite it? [Y/n] ")
                        except NameError:
                            choice = input("Remote resource exists, do you want to overwrite it? [Y/n] ")
                        try:
                            yes = strtobool(choice.lower())
                            if yes:
                                client.upload(remote_path=args.path, local_path=args.from_path, progress=upload_progress)
                                print("\n")
                        except ValueError:
                            print("Incorrect answer")
                except WebDavException as e:
                    logging_exception(e)

        elif action == 'publish':
            if not args.path:
                parser.print_help()
            else:
                options = import_options()
                try:
                    client = Client(options)
                    connection = client.check()
                    if not connection:
                        raise NotConnection(options["webdav_hostname"])
                    link = client.publish(args.path)
                    print(link)
                except WebDavException as e:
                    logging_exception(e)

        elif action == 'unpublish':
            if not args.path:
                parser.print_help()
            else:
                options = import_options()
                try:
                    client = Client(options)
                    connection = client.check()
                    if not connection:
                        raise NotConnection(options["webdav_hostname"])
                    client.unpublish(args.path)
                except WebDavException as e:
                    logging_exception(e)

        elif action == 'push':
            if not args.path or not args.from_path:
                parser.print_help()
            else:
                options = import_options()
                try:
                    client = Client(options)
                    connection = client.check()
                    if not connection:
                        raise NotConnection(options["webdav_hostname"])
                    client.push(remote_directory=args.path, local_directory=args.from_path)
                except WebDavException as e:
                    logging_exception(e)

        elif action == 'pull':
            if not args.path or not args.to_path:
                parser.print_help()
            else:
                options = import_options()
                try:
                    client = Client(options)
                    connection = client.check()
                    if not connection:
                        raise NotConnection(options["webdav_hostname"])
                    client.pull(remote_directory=args.path, local_directory=args.to_path)
                except WebDavException as e:
                    logging_exception(e)

        elif action == 'info':
            if not args.path:
                parser.print_help()
            else:
                options = import_options()
                try:
                    client = Client(options)
                    connection = client.check()
                    if not connection:
                        raise NotConnection(options["webdav_hostname"])
                    info = client.info(args.path)
                    print(info)
                except WebDavException as e:
                    logging_exception(e)

        else:
            parser.print_help()
